package cn.rengy.tpa.wxmp.pay;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.client.fluent.Request;
import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.XmlUtil;
import cn.rengy.lang.DataConverter;
import cn.rengy.lang.ResultEntity;
import cn.rengy.tool.core.util.ExceptionPrintUtils;
import cn.rengy.tool.core.util._DateUtils;
import cn.rengy.tool.core.util._NumberUtils;
import cn.rengy.tool.http.HttpUtils;
import cn.rengy.tpa.wxmp.lang.JaxbParser;
import cn.rengy.tpa.wxmp.pay.exception.PayApiException;
import cn.rengy.tpa.wxmp.pay.exception.PayBusinessException;
import cn.rengy.tpa.wxmp.pay.exception.SignatureException;
import cn.rengy.tpa.wxmp.pay.protocol.AppPayParam;
import cn.rengy.tpa.wxmp.pay.protocol.closeorder.CloseorderRequest;
import cn.rengy.tpa.wxmp.pay.protocol.closeorder.CloseorderResponse;
import cn.rengy.tpa.wxmp.pay.protocol.downloadbill.DownloadbillRequest;
import cn.rengy.tpa.wxmp.pay.protocol.orderquery.OrderqueryRequest;
import cn.rengy.tpa.wxmp.pay.protocol.orderquery.OrderqueryResponse;
import cn.rengy.tpa.wxmp.pay.protocol.pay_result_notify.PayResultCoupon;
import cn.rengy.tpa.wxmp.pay.protocol.pay_result_notify.PayResultDo;
import cn.rengy.tpa.wxmp.pay.protocol.refund.RefundRequest;
import cn.rengy.tpa.wxmp.pay.protocol.refund.RefundResponse;
import cn.rengy.tpa.wxmp.pay.protocol.refundquery.RefundqueryRequest;
import cn.rengy.tpa.wxmp.pay.protocol.refundquery.RefundqueryResponse;
import cn.rengy.tpa.wxmp.pay.protocol.report.ReportRequest;
import cn.rengy.tpa.wxmp.pay.protocol.report.ReportResponse;
import cn.rengy.tpa.wxmp.pay.protocol.unifiedorder.UnifiedorderDTO;
import cn.rengy.tpa.wxmp.pay.protocol.unifiedorder.UnifiedorderRO;
import cn.rengy.tpa.wxmp.pay.protocol.unifiedorder.UnifiedorderRspO;
import cn.rengy.tpa.wxmp.util.WXPayConstants;
import cn.rengy.tpa.wxmp.util.WXPayConstants.SignType;
import cn.rengy.tpa.wxmp.util.WeChatUtil;

/**
 * <h2>支付管理</h2>
 * <p>参考<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3">开发文档</a></p>
 * <p/>
 */
public class PayManager {

    private static Logger logger = LoggerFactory.getLogger(PayManager.class);

    /**
     * 统一下单
     */
    private static final String HTTPS_API_MCH_WEIXIN_QQ_COM_PAY_UNIFIEDORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    /**
     * 查询订单
     */
    private static final String HTTPS_API_MCH_WEIXIN_QQ_COM_PAY_ORDERQUERY = "https://api.mch.weixin.qq.com/pay/orderquery";
    /**
     * 关闭订单
     */
    private static final String HTTPS_API_MCH_WEIXIN_QQ_COM_PAY_CLOSEORDER = "https://api.mch.weixin.qq.com/pay/closeorder";
    /**
     * 申请退款
     */
    private static final String HTTPS_API_MCH_WEIXIN_QQ_COM_SECAPI_PAY_REFUND = "https://api.mch.weixin.qq.com/secapi/pay/refund";
    /**
     * 查询退款
     */
    private static final String HTTPS_API_MCH_WEIXIN_QQ_COM_PAY_REFUNDQUERY = "https://api.mch.weixin.qq.com/pay/refundquery";
    /**
     * 下载对账单
     */
    private static final String HTTPS_API_MCH_WEIXIN_QQ_COM_PAY_DOWNLOADBILL = "https://api.mch.weixin.qq.com/pay/downloadbill";
    /**
     * 测速上报
     */
    private static final String HTTPS_API_MCH_WEIXIN_QQ_COM_PAYITIL_REPORT = "https://api.mch.weixin.qq.com/payitil/report";
    
    
    
    
    /**
     * 统一下单
     */
    public static ResultEntity<UnifiedorderRspO> unifiedorder(DataConverter dataConverter,String mchKey,UnifiedorderDTO unifiedorderDTO) {
    	UnifiedorderRO unifiedorderRO=new UnifiedorderRO();
    	unifiedorderRO.setAppid(unifiedorderDTO.getAppid());
    	unifiedorderRO.setAttach(unifiedorderDTO.getAttach());
    	unifiedorderRO.setBody(unifiedorderDTO.getBody());
    	unifiedorderRO.setDetail(unifiedorderDTO.getDetail());
    	unifiedorderRO.setDevice_info(unifiedorderDTO.getDevice_info());
    	//unifiedorderRO.setFee_type(fee_type);//人民币
    	unifiedorderRO.setGoods_tag(unifiedorderDTO.getGoods_tag());
    	WXPayConstants.LimitPayType limitPayType=unifiedorderDTO.getLimit_pay();
    	unifiedorderRO.setLimit_pay(limitPayType==null?null:limitPayType.name());
    	unifiedorderRO.setMch_id(unifiedorderDTO.getMch_id());
    	String nonce_str=RandomStringUtils.randomNumeric(32);
    	unifiedorderRO.setNonce_str(nonce_str);
    	unifiedorderRO.setNotify_url(unifiedorderDTO.getNotify_url());
    	unifiedorderRO.setOpenid(unifiedorderDTO.getOpenid());
    	unifiedorderRO.setOut_trade_no(unifiedorderDTO.getOut_trade_no());
    	unifiedorderRO.setProduct_id(unifiedorderDTO.getProduct_id());
    	unifiedorderRO.setProfit_sharing(unifiedorderDTO.getProfit_sharing().name());
    	unifiedorderRO.setReceipt(unifiedorderDTO.getReceipt());
    	unifiedorderRO.setScene_info(unifiedorderDTO.getScene_info());
    	
    	unifiedorderRO.setSign_type(WXPayConstants.MD5);
    	unifiedorderRO.setSpbill_create_ip(unifiedorderDTO.getSpbill_create_ip());
    	unifiedorderRO.setTime_expire(unifiedorderDTO.getTime_expire());
    	unifiedorderRO.setTime_start(unifiedorderDTO.getTime_start());
    	unifiedorderRO.setTotal_fee(_NumberUtils.yuanToFen(unifiedorderDTO.getAmount()));
    	unifiedorderRO.setTrade_type(unifiedorderDTO.getTrade_type().name());
    	String sign=WeChatUtil.signatureBean(SignType.MD5, mchKey, unifiedorderRO);
    	unifiedorderRO.setSign(sign);
    	String post=null;
		try {
			post = dataConverter.beanToXml(unifiedorderRO);
		} catch (Exception e) {
			logger.error(ExceptionUtil.stacktraceToString(e,10000));
			return ResultEntity.fail("ERROR","beanToXml解析时出错");
		}
		logger.info("统一下单 post data :{}",post);
		String result=HttpUtils.httpPost(HTTPS_API_MCH_WEIXIN_QQ_COM_PAY_UNIFIEDORDER,MediaType.APPLICATION_XML_VALUE, post);
		logger.info("统一下单response:{}",result);
		
		Document document = XmlUtil.parseXml(result);
    	Map<String,Object> resultMap=XmlUtil.xmlToMap(document.getDocumentElement());
        
        String return_code= MapUtils.getString(resultMap, "return_code");
        if (PayCode.FAIL.toString().equals(return_code)){
        	String return_msg=MapUtils.getString(resultMap,"return_msg");
        	logger.error("统一下单异常,return_msg:{}", return_msg);
        	//throw new PayApiException(PayCode.FAIL, .toString());
        	return ResultEntity.fail(return_msg);
        }
        String result_code=MapUtils.getString(resultMap,"result_code");
        if (PayCode.FAIL.toString().equals(result_code)) {
    		String err_code=MapUtils.getString(resultMap,"err_code");
        	String err_code_des=MapUtils.getString(resultMap,"err_code_des");
        	logger.error("统一下单异常，code:{},mes:{}",err_code,err_code_des);
        	return ResultEntity.fail(err_code,err_code_des);
        }
       
        WeChatUtil.validResponseSign(SignType.MD5, mchKey, resultMap);
        
        UnifiedorderRspO unifiedorderRspO= BeanUtil.mapToBean(resultMap, UnifiedorderRspO.class, true);
		
        return ResultEntity.ok(unifiedorderRspO);
    }
    
    
    
    /**
     * 查询订单
     * <p>参考<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2">开发文档</p>
     *
     * @param request
     * @return
     * @throws SignatureException
     * @throws PayApiException
     * @throws PayBusinessException
     */
    public static OrderqueryResponse orderquery(String mchKey,OrderqueryRequest request) throws SignatureException, PayApiException, PayBusinessException {
        JaxbParser requestParser = buildJAXBParser(OrderqueryRequest.class);
        JaxbParser responseParser = buildJAXBParser(OrderqueryResponse.class);
        request.setSign(WeChatUtil.signatureBean(SignType.MD5, mchKey, request));
        String postData = requestParser.toXML(request);
        logger.info("post data \n{}" , postData);
        String postResult = post(HTTPS_API_MCH_WEIXIN_QQ_COM_PAY_ORDERQUERY, postData);
        logger.info("post result \n{}", postResult);
        Map<String, Object> resultMap=XmlUtil.xmlToMap(postResult);
        
        checkAccess(resultMap);
        checkBusiness(resultMap);
        WeChatUtil.validResponseSign(SignType.MD5,mchKey,resultMap);
        OrderqueryResponse response = (OrderqueryResponse) responseParser.toObj(postResult);
        try {
            parseCouponsForOrderquery(postResult, response);
        } catch (Exception e) {
            logger.error("解析代金券或立减优惠失败", e);
            PayApiException exception = new PayApiException(PayCode.FAIL, "解析代金券或立减优惠失败");
            throw exception;
        }
        return response;
    }

    /**
     * 关闭订单
     * <p>参考<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2">开发文档</p>
     *
     * @param request
     * @return
     * @throws SignatureException
     * @throws PayApiException
     * @throws PayBusinessException
     */
    public static CloseorderResponse closeorder(String mchKey,CloseorderRequest request) throws SignatureException, PayApiException, PayBusinessException {
        JaxbParser requestParser = buildJAXBParser(CloseorderRequest.class);
        JaxbParser responseParser = buildJAXBParser(CloseorderResponse.class);
        request.setSign(WeChatUtil.signatureBean(SignType.MD5, mchKey, request));
        String postData = requestParser.toXML(request);
        logger.info("post data \n{}",  postData);
        String postResult = post(HTTPS_API_MCH_WEIXIN_QQ_COM_PAY_CLOSEORDER, postData);
        logger.info("post result \n{}" , postResult);
        Map<String, Object> resultMap=XmlUtil.xmlToMap(postResult);
        checkAccess(resultMap);
        checkBusiness(resultMap);
        WeChatUtil.validResponseSign(SignType.MD5,mchKey,resultMap);
        CloseorderResponse response = (CloseorderResponse) responseParser.toObj(postResult);
        return response;
    }

    /**
     * 申请退款
     * <p>参考<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4">开发文档</p>
     *
     * @param request
     * @return
     * @throws SignatureException
     * @throws PayApiException
     * @throws PayBusinessException
     * @throws IOException 
     * @throws FileNotFoundException 
     * @throws CertificateException 
     * @throws NoSuchAlgorithmException 
     * @throws KeyStoreException 
     * @throws KeyManagementException 
     * @throws UnrecoverableKeyException 
     */
    public static RefundResponse refund(String certPath,String mchKey,RefundRequest request) throws SignatureException, PayApiException, PayBusinessException, UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
        JaxbParser requestParser = buildJAXBParser(RefundRequest.class);
        JaxbParser responseParser = buildJAXBParser(RefundResponse.class);
        request.setSign(WeChatUtil.signatureBean(SignType.MD5, mchKey, request));
        String postData = requestParser.toXML(request);
        logger.info("post data \n{}" , postData);
        String postResult = postSSL(HTTPS_API_MCH_WEIXIN_QQ_COM_SECAPI_PAY_REFUND, postData,certPath,request.getMch_id());
        logger.info("post result \n{}" ,postResult);
        Map<String, Object> resultMap=XmlUtil.xmlToMap(postResult);
        checkAccess(resultMap);
        checkBusiness(resultMap);
        WeChatUtil.signatureBean(SignType.MD5,mchKey,resultMap);
        RefundResponse response = (RefundResponse) responseParser.toObj(postResult);
        return response;
    }

    /**
     * 查询退款
     * <p>参考<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4">开发文档</p>
     *
     * @param request
     * @return
     * @throws SignatureException
     * @throws PayApiException
     * @throws PayBusinessException
     */
    public static RefundqueryResponse refundquery(String mchKey,RefundqueryRequest request) throws SignatureException, PayApiException, PayBusinessException {
        JaxbParser requestParser = buildJAXBParser(RefundqueryRequest.class);
        JaxbParser responseParser = buildJAXBParser(RefundqueryResponse.class);
        request.setSign(WeChatUtil.signatureBean(SignType.MD5, mchKey, request));
        String postData = requestParser.toXML(request);
        logger.info("post data \n" + postData);
        String postResult = post(HTTPS_API_MCH_WEIXIN_QQ_COM_PAY_REFUNDQUERY, postData);
        logger.info("post result \n" + postResult);
        Map<String, Object> resultMap=XmlUtil.xmlToMap(postResult);
        checkAccess(resultMap);
        checkBusiness(resultMap);
        WeChatUtil.validResponseSign(SignType.MD5,mchKey,resultMap);
        RefundqueryResponse response = (RefundqueryResponse) responseParser.toObj(postResult);
        try {
            parseCouponsForRefundquery(postResult, response);
        } catch (Exception e) {
            logger.error("解析代金券或立减优惠失败", e);
            PayApiException exception = new PayApiException(PayCode.FAIL, "解析代金券或立减优惠失败");
            throw exception;
        }
        return response;
    }

    /**
     * 下载对账单
     * <p>参考<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6">开发文档</p>
     *
     * @param request
     * @return
     * @throws PayApiException
     */
    public static String downloadbill(String mchKey,DownloadbillRequest request) throws PayApiException {
        JaxbParser requestParser = buildJAXBParser(DownloadbillRequest.class);
        request.setSign(WeChatUtil.signatureBean(SignType.MD5, mchKey, request));
        String postData = requestParser.toXML(request);
        logger.info("post data \n" + postData);
        String postResult = post(HTTPS_API_MCH_WEIXIN_QQ_COM_PAY_DOWNLOADBILL, postData);
        logger.info("post result \n" + postResult);
        return postResult;
    }

    /**
     * 测速上报
     * <p>参考<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_8">开发文档</p>
     *
     * @param request
     * @return
     * @throws SignatureException
     * @throws PayApiException
     * @throws PayBusinessException
     */
    public static ReportResponse report(String mchKey,ReportRequest request) throws SignatureException, PayApiException, PayBusinessException {
        JaxbParser requestParser = buildJAXBParser(ReportRequest.class);
        JaxbParser responseParser = buildJAXBParser(ReportResponse.class);
        request.setSign(WeChatUtil.signatureBean(SignType.MD5, mchKey, request));
        String postData = requestParser.toXML(request);
        logger.info("post data \n" + postData);
        String postResult = post(HTTPS_API_MCH_WEIXIN_QQ_COM_PAYITIL_REPORT, postData);
        logger.info("post result \n" + postResult);
        Map<String, Object> resultMap=XmlUtil.xmlToMap(postResult);
        checkAccess(resultMap);
        checkBusiness(resultMap);
        // 测速上报不会返回签名
//        validResponseSign(postResult);
        ReportResponse response = (ReportResponse) responseParser.toObj(postResult);
        return response;
    }

    /**
     * 封装支付结果通知
     * <p/>
     * <b>注意：同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 </b>
     * <p><a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7">开发文档</p>
     *
     * @param servletRequest
     * @return
     * @throws SignatureException
     * @throws PayApiException
     * @throws PayBusinessException
     */
    public static ResultEntity<PayResultDo> parsePayResultNotify(InputStream inputStream,MchKeyCallable mchKeyCallable)  {
    	String resultStr=IoUtil.read(inputStream, StandardCharsets.UTF_8);
    	logger.info("支付结果报文:{}" ,resultStr);
    	Document document = XmlUtil.parseXml(resultStr);
    	//Document document = XmlUtil.readXML(inputStream);
    	Map<String,Object> resultMap=XmlUtil.xmlToMap(document.getDocumentElement());
        logger.info("支付结果通知:{}" ,resultMap.toString());
        
        String return_code= MapUtils.getString(resultMap, "return_code");
        if (PayCode.FAIL.toString().equals(return_code)){
        	String return_msg=MapUtils.getString(resultMap,"return_msg");
        	logger.error("返回状态码异常,return_msg:{}", return_msg);
        	//throw new PayApiException(PayCode.FAIL, .toString());
        	return ResultEntity.fail(return_msg);
        }
        String result_code=MapUtils.getString(resultMap,"result_code");
        if (PayCode.FAIL.toString().equals(result_code)) {
    		String err_code=MapUtils.getString(resultMap,"result_code");
        	String err_code_des=MapUtils.getString(resultMap,"result_code");
        	logger.error("支付失败，code:{},mes:{}",err_code,err_code_des);
        	return ResultEntity.fail(err_code,err_code_des);
        }
        
        String mch_id=MapUtils.getString(resultMap,"mch_id");
        String mchKey=mchKeyCallable.get(mch_id);
        WeChatUtil.validResponseSign(SignType.MD5,mchKey,resultMap);
    	PayResultDo payResultDo =parsePayResultNotifyResponse(resultMap);
        return ResultEntity.ok(payResultDo);
    }

    private static PayResultDo parsePayResultNotifyResponse(Map<String, Object> resultMap) {
    	PayResultDo response=new PayResultDo();
//    		String return_code=resultMap.get("return_code");//SUCCESS/FAIL 此字段是通信标识，非交易标识，交易是否成功需要查看result_code来判断
//    		String return_msg=resultMap.get("return_msg");//返回信息，如非空，为错误原因 签名失败 参数格式校验错误
//    		String result_code=resultMap.get("result_code");//业务结果  是 String(16) SUCCESS SUCCESS/FAIL 
//    		String err_code=resultMap.get("err_code");//错误代码  否 String(32) SYSTEMERROR 错误返回的信息描述 
//    		String err_code_des=resultMap.get("err_code_des");//错误代码描述  否 String(128) 系统错误 错误返回的信息描述 
		String appid=MapUtils.getString(resultMap,"appid");
		response.setAppid(appid);
		String mch_id=MapUtils.getString(resultMap,"mch_id");
		response.setMch_id(mch_id);
		String device_info=MapUtils.getString(resultMap,"device_info");//微信支付分配的终端设备号，
		response.setDevice_info(device_info);
		//String nonce_str=resultMap.get("nonce_str");//随机字符串，不长于32位
		//response.setNonce_str(nonce_str);
		//String sign=resultMap.get("sign");//签名
		//response.setSign(sign);
		//String sign_type=resultMap.get("sign_type");//签名类型  否 String(32) HMAC-SHA256 签名类型，目前支持HMAC-SHA256和MD5，默认为MD5 
		//response.setSign_type(sign_type);
		String openid=MapUtils.getString(resultMap,"openid");//用户在商户appid下的唯一标识 
		response.setOpenid(openid);
		String is_subscribe=MapUtils.getString(resultMap,"is_subscribe");//是否关注公众账号 Y 用户是否关注公众账号，Y-关注，N-未关注 
		if("Y".equals(is_subscribe)) {
			response.setIs_subscribe(Boolean.TRUE);
		}else {
			response.setIs_subscribe(Boolean.FALSE);
		}
		String trade_type=MapUtils.getString(resultMap,"trade_type");//JSAPI JSAPI、NATIVE、APP
		response.setTrade_type(WXPayConstants.TradeType.valueOf(trade_type));
		String bank_type=MapUtils.getString(resultMap,"bank_type");//银行类型，采用字符串类型的银行标识，银行类型见银行列表
		response.setBank_type(bank_type);
		String total_fee=MapUtils.getString(resultMap,"total_fee");//订单金额  是 Int 100 订单总金额，单位为分 
		response.setTotal_fee(_NumberUtils.fenToYuan(total_fee));
		String settlement_total_fee=MapUtils.getString(resultMap,"settlement_total_fee");//应结订单金额=订单金额-非充值代金券金额，应结订单金额<=订单金额。
		response.setSettlement_total_fee(_NumberUtils.fenToYuan(settlement_total_fee));
		String fee_type =MapUtils.getString(resultMap,"fee_type");//货币种类  否 String(8) CNY 货币类型，符合ISO4217标准的三位字母代码，默认人民币：CNY，其他值列表详见货币类型 
		response.setFee_type(fee_type);
		String cash_fee=MapUtils.getString(resultMap,"cash_fee");//现金支付金额  是 Int 100 现金支付金额
		response.setCash_fee(_NumberUtils.fenToYuan(cash_fee));
		String cash_fee_type=MapUtils.getString(resultMap,"cash_fee_type");//CNY 货币类型，符合ISO4217标准的三位字母代码，默认人民币：CNY，其他值列表详见货币类型 
		response.setCash_fee_type(cash_fee_type);
		String coupon_fee=MapUtils.getString(resultMap,"coupon_fee");//总代金券金额  否 Int 10 代金券金额<=订单金额，订单金额-代金券金额=现金支付金额
		response.setCoupon_fee(_NumberUtils.fenToYuan(coupon_fee));
		//代金券使用数量  否 Int 1 代金券使用数量 
		Integer coupon_count=MapUtils.getInteger(resultMap,"coupon_count");
		response.setCoupon_count(coupon_count);
		if(coupon_count!=null&&coupon_count>0) {
			List<PayResultCoupon> list=new ArrayList<PayResultCoupon>(coupon_count);
			for(int i=0;i<list.size();i++){
				PayResultCoupon payResultCoupon=new PayResultCoupon();
				String coupon_type_$n=MapUtils.getString(resultMap,"coupon_type_"+i);;//CASH--充值代金券 NO_CASH---非充值代金券 并且订单使用了免充值券后有返回（取值：CASH、NO_CASH）。$n为下标,从0开始编号，举例：coupon_type_0注意：只有下单时订单使用了优惠，回调通知才会返回券信息。
        		String coupon_id_$n=MapUtils.getString(resultMap,"coupon_id_"+i);//代金券ID  否 String(20)  10000 代金券ID,$n为下标，从0开始编号
        		String coupon_fee_$n=MapUtils.getString(resultMap,"coupon_fee_"+i);//单个代金券支付金额 $n为下标，从0开始编号 
        		payResultCoupon.setCoupon_fee(_NumberUtils.fenToYuan(coupon_fee_$n));
        		payResultCoupon.setCoupon_id(coupon_id_$n);
        		if(StringUtils.isNotBlank(coupon_type_$n)){
        			payResultCoupon.setCoupon_type(CouponType.valueOf(coupon_type_$n));
        		}
        		list.add(payResultCoupon);
			}
		}
		String transaction_id=MapUtils.getString(resultMap,"transaction_id");//微信支付订单号 transaction_id 是 String(32) 
		response.setTransaction_id(transaction_id);
		String out_trade_no=MapUtils.getString(resultMap,"out_trade_no");//商户系统内部订单号，要求32个字符内，
		response.setOut_trade_no(out_trade_no);
		String attach=MapUtils.getString(resultMap,"attach");//商家数据包，原样返回 
		response.setAttach(attach);
		String time_end=MapUtils.getString(resultMap,"time_end");//支付完成时间 格式为yyyyMMddHHmmss
		response.setTime_end(_DateUtils.parseDate(time_end, "yyyyMMddHHmmss"));
    	return response;
    }
    /**
     * 商户处理支付结果通知后同步返回给微信参数
     *
     * @param servletResponse
     * @param postData
     * @throws PayApiException
     */
//    private static void responseToWechat(ServletResponse servletResponse, String postData) throws PayApiException {
//        try {
//            servletResponse.getOutputStream().write(postData.getBytes(Consts.UTF_8));
//            servletResponse.getOutputStream().flush();
//            servletResponse.getOutputStream().close();
//        } catch (IOException e) {
//            throw new PayApiException(PayCode.FAIL, "支付结果通知同步返回失败");
//        }
//    }

    /**
     * 解析支付结果通知的代金券或立减优惠
     *
     * @param postResult
     * @param payResultNotifyResponse
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
//    private static void parseCouponsForPayResultNotify(String postResult, PayResultDo payResultNotifyResponse) throws ParserConfigurationException, IOException, SAXException {
//        List<String> coupon_id_$n = new ArrayList<String>();
//        List<Integer> coupon_fee_$n = new ArrayList<Integer>();
//        Map<String, String> mapFromPayResultNotifyXML = getMapFromXMLString(postResult);
//        Iterator<String> iterator = mapFromPayResultNotifyXML.keySet().iterator();
//        while (iterator.hasNext()) {
//            String key = iterator.next();
//            // 解析代金券或立减优惠，$n为下标，从0开始编号
//            if (key.matches("^coupon_id_[0-9]+$")) { // coupon_id_$n
//                coupon_id_$n.add(mapFromPayResultNotifyXML.get(key).toString());
//            } else if (key.matches("^coupon_fee_[0-9]+$")) { // coupon_fee_$n
//                coupon_fee_$n.add(Integer.valueOf(mapFromPayResultNotifyXML.get(key).toString()));
//            }
//        }
//        payResultNotifyResponse.setCoupon_id_$n(coupon_id_$n.toArray(new String[coupon_id_$n.size()]));
//        payResultNotifyResponse.setCoupon_fee_$n(coupon_fee_$n.toArray(new Integer[coupon_fee_$n.size()]));
//    }

    /**
     * 解析查询订单的代金券或立减优惠
     *
     * @param postResult
     * @param orderqueryResponse
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    private static void parseCouponsForOrderquery(String postResult, OrderqueryResponse orderqueryResponse) throws ParserConfigurationException, IOException, SAXException {
        List<String> coupon_batch_id_$n = new ArrayList<String>();
        List<String> coupon_id_$n = new ArrayList<String>();
        List<Integer> coupon_fee_$n = new ArrayList<Integer>();
        Map<String, Object> mapFromPayResultNotifyXML=XmlUtil.xmlToMap(postResult);
        Iterator<String> iterator = mapFromPayResultNotifyXML.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            // 解析代金券或立减优惠，$n为下标，从0开始编号
            if (key.matches("^coupon_batch_id_[0-9]+$")) { // coupon_batch_id_$n
                coupon_batch_id_$n.add(mapFromPayResultNotifyXML.get(key).toString());
            } else if (key.matches("^coupon_id_[0-9]+$")) { // coupon_id_$n
                coupon_id_$n.add(mapFromPayResultNotifyXML.get(key).toString());
            } else if (key.matches("^coupon_fee_[0-9]+$")) { // coupon_fee_$n
                coupon_fee_$n.add(Integer.valueOf(mapFromPayResultNotifyXML.get(key).toString()));
            }
        }
        orderqueryResponse.setCoupon_batch_id_$n(coupon_batch_id_$n.toArray(new String[coupon_id_$n.size()]));
        orderqueryResponse.setCoupon_id_$n(coupon_id_$n.toArray(new String[coupon_id_$n.size()]));
        orderqueryResponse.setCoupon_fee_$n(coupon_fee_$n.toArray(new Integer[coupon_fee_$n.size()]));
    }

    /**
     * 解析查询退款的代金券或立减优惠
     *
     * @param postResult
     * @param refundqueryResponse
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    private static void parseCouponsForRefundquery(String postResult, RefundqueryResponse refundqueryResponse) throws ParserConfigurationException, IOException, SAXException {
        List<String> out_refund_no_$n = new ArrayList<String>();
        List<String> refund_id_$n = new ArrayList<String>();
        List<String> refund_channel_$n = new ArrayList<String>();
        List<Integer> refund_fee_$n = new ArrayList<Integer>();
        List<Integer> coupon_refund_fee_$n = new ArrayList<Integer>();
        List<Integer> coupon_refund_count_$n = new ArrayList<Integer>();
        List<List<String>> coupon_refund_batch_id_$n_$m = new ArrayList<List<String>>();
        List<List<String>> coupon_refund_id_$n_$m = new ArrayList<List<String>>();
        List<List<Integer>> coupon_refund_fee_$n_$m = new ArrayList<List<Integer>>();
        List<String> refund_status_$n = new ArrayList<String>();
        Map<String, Object> mapFromPayResultNotifyXML=XmlUtil.xmlToMap(postResult);
        Iterator<String> iterator = mapFromPayResultNotifyXML.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            // 解析代金券或立减优惠，$n为下标，$n为下标，从0开始编号
            if (key.matches("^out_refund_no_[0-9]+$")) { // out_refund_no_$n
                out_refund_no_$n.add(mapFromPayResultNotifyXML.get(key).toString());
            } else if (key.matches("^refund_id_[0-9]+$")) { // refund_id_$n
                refund_id_$n.add(mapFromPayResultNotifyXML.get(key).toString());
            } else if (key.matches("^refund_channel_[0-9]+$")) { // refund_channel_$n
                refund_channel_$n.add(mapFromPayResultNotifyXML.get(key).toString());
            } else if (key.matches("^refund_fee_[0-9]+$")) { // refund_fee_$n
                refund_fee_$n.add(Integer.valueOf(mapFromPayResultNotifyXML.get(key).toString()));
            } else if (key.matches("^coupon_refund_fee_[0-9]+$")) { // coupon_refund_fee_$n
                coupon_refund_fee_$n.add(Integer.valueOf(mapFromPayResultNotifyXML.get(key).toString()));
            } else if (key.matches("^coupon_refund_fee_[0-9]+$")) { // coupon_refund_fee_$n
                coupon_refund_fee_$n.add(Integer.valueOf(mapFromPayResultNotifyXML.get(key).toString()));
            } else if (key.matches("^coupon_refund_count_[0-9]+$")) { // coupon_refund_count_$n
                coupon_refund_count_$n.add(Integer.valueOf(mapFromPayResultNotifyXML.get(key).toString()));
            } else if (key.matches("^coupon_refund_batch_id_[0-9]+_[0-9]+$")) { // coupon_refund_batch_id_$n_$m
                String[] indexs = key.replace("coupon_refund_batch_id_", "").split("_");
                int index0 = Integer.valueOf(indexs[0]);
                if (coupon_refund_batch_id_$n_$m.size() <= index0) {
                    coupon_refund_batch_id_$n_$m.add(new ArrayList<String>());
                }
                coupon_refund_batch_id_$n_$m.get(index0).add(mapFromPayResultNotifyXML.get(key).toString());
            } else if (key.matches("^coupon_refund_id_[0-9]+_[0-9]+$")) { // coupon_refund_id_$n_$m
                String[] indexs = key.replace("coupon_refund_id_", "").split("_");
                int index0 = Integer.valueOf(indexs[0]);
                if (coupon_refund_id_$n_$m.size() <= index0) {
                    coupon_refund_id_$n_$m.add(new ArrayList<String>());
                }
                coupon_refund_id_$n_$m.get(index0).add(mapFromPayResultNotifyXML.get(key).toString());
            } else if (key.matches("^coupon_refund_fee_[0-9]+_[0-9]+$")) { // coupon_refund_fee_$n_$m
                String[] indexs = key.replace("coupon_refund_fee_", "").split("_");
                int index0 = Integer.valueOf(indexs[0]);
                if (coupon_refund_fee_$n_$m.size() <= index0) {
                    coupon_refund_fee_$n_$m.add(new ArrayList<Integer>());
                }
                coupon_refund_fee_$n_$m.get(index0).add(Integer.valueOf(mapFromPayResultNotifyXML.get(key).toString()));
            } else if (key.matches("^refund_status_[0-9]+$")) { // refund_status_$n
                refund_status_$n.add(mapFromPayResultNotifyXML.get(key).toString());
            }
        }
        refundqueryResponse.setOut_refund_no_$n(out_refund_no_$n.toArray(new String[out_refund_no_$n.size()]));
        refundqueryResponse.setRefund_id_$n(refund_id_$n.toArray(new String[refund_id_$n.size()]));
        refundqueryResponse.setRefund_channel_$n(refund_channel_$n.toArray(new String[refund_channel_$n.size()]));
        refundqueryResponse.setRefund_fee_$n(refund_fee_$n.toArray(new Integer[refund_fee_$n.size()]));
        refundqueryResponse.setCoupon_refund_fee_$n(coupon_refund_fee_$n.toArray(new Integer[coupon_refund_fee_$n.size()]));
        refundqueryResponse.setCoupon_refund_count_$n(coupon_refund_count_$n.toArray(new Integer[coupon_refund_count_$n.size()]));
        String[][] coupon_refund_batch_id_$n_$m_array = new String[coupon_refund_batch_id_$n_$m.size()][];
        for (int i = 0; i < coupon_refund_batch_id_$n_$m.size(); i++) {
            List<String> item = coupon_refund_batch_id_$n_$m.get(i);
            coupon_refund_batch_id_$n_$m_array[i] = item.toArray(new String[item.size()]);
        }
        refundqueryResponse.setCoupon_refund_batch_id_$n_$m(coupon_refund_batch_id_$n_$m_array);
        String[][] coupon_refund_id_$n_$m_array = new String[coupon_refund_id_$n_$m.size()][];
        for (int i = 0; i < coupon_refund_id_$n_$m.size(); i++) {
            List<String> item = coupon_refund_id_$n_$m.get(i);
            coupon_refund_id_$n_$m_array[i] = item.toArray(new String[item.size()]);
        }
        refundqueryResponse.setCoupon_refund_id_$n_$m(coupon_refund_id_$n_$m_array);
        Integer[][] coupon_refund_fee_$n_$m_array = new Integer[coupon_refund_fee_$n_$m.size()][];
        for (int i = 0; i < coupon_refund_fee_$n_$m.size(); i++) {
            List<Integer> item = coupon_refund_fee_$n_$m.get(i);
            coupon_refund_fee_$n_$m_array[i] = item.toArray(new Integer[item.size()]);
        }
        refundqueryResponse.setCoupon_refund_fee_$n_$m(coupon_refund_fee_$n_$m_array);
        refundqueryResponse.setRefund_status_$n(refund_status_$n.toArray(new String[refund_status_$n.size()]));
    }

    /**
     * 构造H5调用支付的参数对象
     *
     * @param timeStamp
     * @param nonceStr
     * @param prepayId
     * @return
     */
    public static H5PayParam buildH5PayConfig(String mchKey,String appid,String timeStamp, String nonceStr, String prepayId) {
        H5PayParam config = new H5PayParam();
        config.setAppId(appid);
        config.setTimeStamp(timeStamp);
        config.setNonceStr(nonceStr);
        config.setPackageWithPrepayId("prepay_id=".concat(prepayId));
        config.setPaySign(WeChatUtil.signatureBean(SignType.MD5, mchKey, config));
        return config;
    }
    public static AppPayParam buildAppPayParam(String mchKey,String appid,String partnerid , String prepayid) {
    	AppPayParam config = new AppPayParam();
        config.setAppid(appid);
        config.setPartnerid(partnerid);
        config.setPrepayid(prepayid);
        Long time=System.currentTimeMillis()/1000;
        config.setTimestamp(time.toString());
        String nonceStr=RandomStringUtils.randomAlphanumeric(32);
        config.setNoncestr(nonceStr);
        config.setSign(WeChatUtil.signatureBean(SignType.MD5, mchKey, config));
        return config;
    }
    /**
     * 检查响应结果是否正确
     *
     * @param postResult
     */
    private static void checkAccess(Map<String, Object> map) throws PayApiException {
        if (PayCode.FAIL.equals(map.get("return_code").toString())) {
        	throw new PayApiException(PayCode.FAIL, map.get("return_msg").toString());
        }
    }

    /**
     * 检查业务结果是否正确
     *
     * @param postResult
     */
    private static void checkBusiness(Map<String, Object> map) throws PayBusinessException {
    	if (PayCode.FAIL.toString().equals((String)map.get("result_code"))) {
    		String err_code=(String)map.get("result_code");
        	String err_code_des=(String)map.get("result_code");
        	logger.error("支付失败，code:{},mes:{}",err_code,err_code_des);
        	throw new PayBusinessException(PayCode.FAIL, err_code, err_code_des);
        }
    }

    

    /**
     * XML串转化成Map
     *
     * @param xmlString
     * @return
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
//    private static Map<String, Object> getMapFromXMLString(String xmlString) throws ParserConfigurationException, SAXException, IOException {
//        Document document = XmlUtil.parseXml(xmlString.trim());
//        //获取到document里面的全部结点
//        NodeList allNodes = document.getFirstChild().getChildNodes();
//        Map<String, Object> map = new LinkedHashMap<String, Object>();
//        int i = 0;
//        while (i < allNodes.getLength()) {
//        	Node node = allNodes.item(i);
//            if (node instanceof Element) {
//                map.put(node.getNodeName(), node.getTextContent());
//            }
//            i++;
//        }
//        return map;
//
//    }

    /**
     * 构建XML解析器JAXBPaser
     *
     * @param clazz
     * @return
     */
    private static JaxbParser buildJAXBParser(Class clazz) {
        JaxbParser parser = new JaxbParser(clazz);
        Field[] fields = clazz.getDeclaredFields();
        String[] fieldNames = new String[fields.length];
        for (int i = 0; i < fields.length; i++) {
            fieldNames[i] = fields[i].getName();
        }
        parser.setCdataNode(fieldNames);
        return parser;
    }

    /**
     * post 请求
     *
     * @param url
     * @param xml
     * @return
     */
    private static String post(String url, String xml) {
        try {
            HttpEntity entity = Request.Post(url)
                    .bodyString(xml, ContentType.create("text/xml", StandardCharsets.UTF_8.name()))
                    .execute().returnResponse().getEntity();
            if (entity != null) {
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                entity.writeTo(byteArrayOutputStream);
                return byteArrayOutputStream.toString(StandardCharsets.UTF_8.name());
            }
            return null;
        } catch (Exception e) {
            logger.error("post请求异常，" ,e.getMessage());
            logger.error(ExceptionPrintUtils.getTrace(e));
        }
        return null;
    }
    public static String postSSL(String url, String data, String certPath, String certPass) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException {
		KeyStore clientStore = KeyStore.getInstance("PKCS12");
		clientStore.load(new FileInputStream(certPath), certPass.toCharArray());
		KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
		kmf.init(clientStore, certPass.toCharArray());
		KeyManager[] kms = kmf.getKeyManagers();
		SSLContext sslContext = SSLContext.getInstance("TLSv1");
		
		sslContext.init(kms, null, new SecureRandom());
		HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
		URL _url = new URL(url);
		HttpsURLConnection conn = (HttpsURLConnection) _url.openConnection();
		
		conn.setConnectTimeout(25000);
		conn.setReadTimeout(25000);
		conn.setRequestMethod("POST");
		conn.setDoOutput(true);
		conn.setDoInput(true);
		
		conn.setRequestProperty("Content-Type",ContentType.create("application/x-www-form-urlencoded", StandardCharsets.UTF_8.name()).toString());
		//conn.setRequestProperty("User-Agent", DEFAULT_USER_AGENT);
		conn.connect();
		try(OutputStream out = conn.getOutputStream();
			InputStream inputStream = conn.getInputStream();
			BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
		) {
			
			
			
			out.write(data.getBytes(StandardCharsets.UTF_8));
			out.flush();
			
			
			StringBuilder sb = new StringBuilder();
			String line = null;
			while ((line = reader.readLine()) != null){
				sb.append(line).append("\n");
			}
			return sb.toString();
		}
	}
}
